using System.Collections.Generic;
using System.Text;

namespace org.jawk.jrt
{
	/// <summary>An AWK associative array.</summary>
	/// <remarks>
	/// An AWK associative array.
	/// <p>
	/// The implementation requires the ability to choose,
	/// at runtime, whether the keys are to be maintained in
	/// sorted order or not.  Therefore, the implementation
	/// contains a reference to a Map (either TreeMap or
	/// HashMap, depending on whether to maintain keys in
	/// sorted order or not) and delegates calls to it
	/// accordingly.
	/// </remarks>
	public class AssocArray : IComparer<object>
	{
		protected System.Collections.Generic.IDictionary<object, object> map;

		public AssocArray(bool sorted_array_keys)
		{
			if (sorted_array_keys)
			{
				map = new System.Collections.Generic.SortedList<object, object>(this);
			}
			else
			{
				map = new System.Collections.Generic.Dictionary<object, object>();
			}
		}

		/// <summary>
		/// The parameter to useMapType to convert
		/// this associative array to a HashMap.
		/// </summary>
		/// <remarks>
		/// The parameter to useMapType to convert
		/// this associative array to a HashMap.
		/// </remarks>
		public const int MT_HASH = 2 << 0;

		/// <summary>
		/// The parameter to useMapType to convert
		/// this associative array to a LinkedHashMap.
		/// </summary>
		/// <remarks>
		/// The parameter to useMapType to convert
		/// this associative array to a LinkedHashMap.
		/// </remarks>
		public const int MT_LINKED = 2 << 1;

		/// <summary>
		/// The parameter to useMapType to convert
		/// this associative array to a TreeMap.
		/// </summary>
		/// <remarks>
		/// The parameter to useMapType to convert
		/// this associative array to a TreeMap.
		/// </remarks>
		public const int MT_TREE = 2 << 2;

		/// <summary>
		/// Convert the map which backs this associative array
		/// into one of HashMap, LinkedHashMap, or TreeMap.
		/// </summary>
		/// <remarks>
		/// Convert the map which backs this associative array
		/// into one of HashMap, LinkedHashMap, or TreeMap.
		/// </remarks>
		/// <param name="map_type">
		/// Can be one of MT_HASH, MT_LINKED,
		/// or MT_TREE.
		/// </param>
		public virtual void UseMapType(int map_type)
		{
			switch (map_type)
			{
				case MT_HASH:
				{
					//assert map.size() == 0;
					map = new System.Collections.Generic.Dictionary<object, object>();
					break;
				}

				case MT_LINKED:
				{
					map = new Dictionary<object, object>(); 
					break;
				}

				case MT_TREE:
				{
					map = new System.Collections.Generic.SortedList<object, object>(this);
					break;
				}

				default:
				{
					throw new System.Exception("Invalid map type : " + map_type);
				}
			}
		}

		/// <summary>
		/// Provide a string representation of the delegated
		/// map object.
		/// </summary>
		/// <remarks>
		/// Provide a string representation of the delegated
		/// map object.
		/// It exists to support the _DUMP keyword.
		/// </remarks>
		public virtual string MapString()
		{
			// was:
			//return map.toString();
			// but since the extensions, assoc arrays can become keys as well
			StringBuilder sb = new StringBuilder("{");
			int cnt = 0;
			foreach (object o in map.Keys)
			{
				if (cnt > 0)
				{
					sb.Append(", ");
				}
				if (o is org.jawk.jrt.AssocArray)
				{
					sb.Append(((org.jawk.jrt.AssocArray)o).MapString());
				}
				else
				{
					sb.Append(o.ToString());
				}
				//sb.append('=').append(map.get(o));
				sb.Append('=');
				object o2 = map[o];
				if (o2 is org.jawk.jrt.AssocArray)
				{
					sb.Append(((org.jawk.jrt.AssocArray)o2).MapString());
				}
				else
				{
					sb.Append(o2.ToString());
				}
				++cnt;
			}
			return sb.Append('}').ToString();
		}

		private static readonly string BLANK = string.Empty;

		// a "null" value in Awk
		/// <summary>
		/// Test whether a particular key is
		/// contained within the associative array.
		/// </summary>
		/// <remarks>
		/// Test whether a particular key is
		/// contained within the associative array.
		/// Unlike get(), which adds a blank (null)
		/// reference to the associative array if the
		/// element is not found, isIn will not.
		/// It exists to support the IN keyword.
		/// </remarks>
		public virtual bool IsIn(object key)
		{
			return map.ContainsKey(key);
		}

		/// <summary>
		/// Get the value of an associative array
		/// element given a particular key.
		/// </summary>
		/// <remarks>
		/// Get the value of an associative array
		/// element given a particular key.
		/// If the key does not exist, a null value
		/// (blank string) is inserted into the array
		/// with this key, and the null value is returned.
		/// </remarks>
		public virtual object Get(object key)
		{
            object result = null;
            if(map.ContainsKey(key))
		    {
                result = map[key];
		    }
		    
			if (result == null)
			{
				// based on the AWK specification:
				// Any reference (except for IN expressions) to a non-existent
				// array element will automatically create it.
				map[key] = result = BLANK;
			}
			return result;
		}

		public virtual object Put(object key, object value)
		{
			return map[key] = value;
		}

		/// <summary>Added to support insertion of primitive key types.</summary>
		/// <remarks>Added to support insertion of primitive key types.</remarks>
		public virtual object Put(int key, object value)
		{
			return map[key] = value;
		}

		public virtual System.Collections.Generic.ICollection<object> KeySet()
		{
			return map.Keys;
		}

		public virtual void Clear()
		{
			map.Clear();
		}

		public virtual object Remove(object key)
		{
            object o = null;
            if(map.ContainsKey(key))
            {
                o = map[key];
            }
            map.Remove(key);
            return o;
		}

		public override string ToString()
		{
			throw new org.jawk.jrt.AwkRuntimeException("Cannot evaluate an unindexed array.");
		}

		/// <summary>
		/// Comparator implementation used by the TreeMap
		/// when keys are to be maintained in sorted order.
		/// </summary>
		/// <remarks>
		/// Comparator implementation used by the TreeMap
		/// when keys are to be maintained in sorted order.
		/// </remarks>
		public virtual int Compare(object o1, object o2)
		{
			if (o1 is string || o2 is string)
			{
				// use string comparison
				string s1 = o1.ToString();
				string s2 = o2.ToString();
                return s1.CompareTo(s2);
			}
			else
			{
				if (o2 is double || o2 is double)
				{
					double d1 = ((double)o1);
					double d2 = ((double)o2);
					return d1.CompareTo(d2);
				}
				else
				{
					int i1 = ((int)o1);
					int i2 = ((int)o2);
					return i1.CompareTo(i2);
				}
			}
		}

		public virtual string GetMapVersion()
		{
            return string.Format("{0}.{1}.{2}.{3}", 
                map.GetType().Assembly.GetName().Version.Major,
                map.GetType().Assembly.GetName().Version.Minor,
                map.GetType().Assembly.GetName().Version.Revision,
                map.GetType().Assembly.GetName().Version.Build);
		}
	}
}
